package com.craigcmiller.cmtetris.core;

import java.util.ArrayList;
import java.awt.Color;


/**
 * Tetris block
 * @author craig
 */
public abstract class Block
{
	protected class BlockCell
	{
		public BlockCell(int x, int y)
		{
			this.x=x;
			this.y=y;
		}
		
		
		public int x, y;
	}
	
	
	protected ArrayList<BlockCell> cells;
	
	protected Board board;
	
	private Color blockColor;
	
	
	/**
	 * Construct a block
	 * @param board
	 */
	public Block(Board board)
	{
		this.setup(board);
	}
	
	
	/**
	 * Setup the block
	 * @param board
	 */
	private void setup(Board board)
	{
		this.board=board;
		
		this.cells=new ArrayList<BlockCell>(4);
		
		this.build();
		
		this.blockColor=new Color(
				Board.getRandomizer().nextInt(255), Board.getRandomizer().nextInt(255), Board.getRandomizer().nextInt(255));
		
		// Iterate through all cells of the block
		//for (BlockCell bc : cells) {
			//Cell c=this.getCellForBlockCell(bc);
			
			// Check all cells are empty before filling them
			/*if (c.getCellState()==Cell.CellState.set) {
				System.out.println("setup() game over");
				board.setGameOver();
				return;
			} else {*/
			//	c.setCellState(Cell.CellState.empty);
				
			//	c.setCellColor(new Color(blockColor.getRed(), blockColor.getGreen(), blockColor.getBlue(), 128));
			//}
		//}
	}
	
	
	/**
	 * Make the block active
	 */
	public void activateBlock()
	{
		for (BlockCell bc : cells) {
			Cell c=this.getCellForBlockCell(bc);
			
			c.setCellColor(blockColor);
			c.setCellState(Cell.CellState.set);
		}
	}
	
	
	/**
	 * Show the block as the next block
	 */
	public void showAsNextBlock()
	{
		Color transparentColor=new Color(blockColor.getRed(), blockColor.getGreen(), blockColor.getBlue(), 128);
		
		for (BlockCell bc : cells) {
			Cell c=this.getCellForBlockCell(bc);
			
			c.setCellColor(transparentColor);
			c.setCellState(Cell.CellState.set);
		}
	}
	
	
	/**
	 * Update board cell states for a block's cells
	 * @param newCellState
	 */
	public void setCellStatesForBlock(Cell.CellState newCellState)
	{
		// Iterate through all cells of the block
		for (BlockCell bc : cells)
			this.getCellForBlockCell(bc).setCellState(newCellState);
	}
	
	
	/**
	 * Get a board cell for a block cell
	 * @param blockCell
	 * @return
	 */
	protected Cell getCellForBlockCell(BlockCell blockCell)
	{
		return board.getCellAt(blockCell.x, blockCell.y);
	}
	
	
	/**
	 * Check if a cell move is legal
	 * @param idx
	 * @param xoffset
	 * @param yoffset
	 * @return
	 */
	protected boolean isCellMoveLegal(int idx, int xoffset, int yoffset)
	{
		Cell c=board.getCellAt(cells.get(idx).x+xoffset, cells.get(idx).y+yoffset);
		
		if (c==null) return false;
		
		return c.getCellState() !=Cell.CellState.set;
	}
	
	
	/**
	 * Move a single cell by an offset. Does not check if move is legal
	 * @param idx
	 * @param xoffset
	 * @param yoffset
	 */
	protected void moveCell(int idx, int xoffset, int yoffset)
	{
		board.getCellAt(cells.get(idx).x+xoffset, cells.get(idx).y+yoffset).copyPropertiesFromCell(
				board.getCellAt(cells.get(idx).x, cells.get(idx).y));
		
		board.getCellAt(cells.get(idx).x, cells.get(idx).y).setCellState(Cell.CellState.empty);

		cells.get(idx).x+=xoffset;
		cells.get(idx).y+=yoffset;

		//board.getCellAt(cells.get(idx).x, cells.get(idx).y).setCellState(Cell.CellState.set);
	}
	
	
	/**
	 * Check if it is safe to move by a certain offset
	 * @param xoffset
	 * @param yoffset
	 * @return
	 */
	protected boolean isSafeMove(int xoffset, int yoffset)
	{
		// Deactivate the current cells so we do not look at them
		this.setCellStatesForBlock(Cell.CellState.empty);
		
		for (BlockCell bc : cells)
			if (bc.y+yoffset==board.getHeight() || bc.x+xoffset<0 || bc.x+xoffset>=board.getWidth()
					|| board.getCellAt(bc.x+xoffset, bc.y+yoffset).getCellState()==Cell.CellState.set) {
				// Reactivate the current cells
				this.setCellStatesForBlock(Cell.CellState.set);
				
				return false;
			}
		
		// Reactivate the current cells
		this.setCellStatesForBlock(Cell.CellState.set);
		
		return true;
	}
	
	
	/**
	 * Move the block by an offset
	 * @param xoffset
	 * @param yoffset
	 */
	protected void moveBlock(int xoffset, int yoffset)
	{
		this.setCellStatesForBlock(Cell.CellState.empty);
		
		for (BlockCell bc : cells) {
			board.getCellAt(bc.x+xoffset, bc.y+yoffset).copyPropertiesFromCell(
					board.getCellAt(bc.x, bc.y));
			
			bc.x+=xoffset;
			bc.y+=yoffset;
		}
		
		this.setCellStatesForBlock(Cell.CellState.set);
		
		board.updateNextBlock();
	}
	
	
	/**
	 * Implement to provide initial creation of the block
	 */
	protected abstract void build();
	
	
	/**
	 * Implement to provide block type specific rotation
	 */
	public abstract void rotate();
	
	
	/**
	 * Move the block down one
	 */
	public void moveDown()
	{
		if (this.isSafeMove(0, 1)) this.moveBlock(0, 1);
		else if (this.getLowestCellPosition()==0) board.setGameOver();
		else board.addRandomBlock();
	}
	
	
	/**
	 * Move the block left one
	 */
	public void moveLeft()
	{
		if (this.isSafeMove(-1, 0)) this.moveBlock(-1, 0);
	}
	
	
	/**
	 * Move the block right one
	 */
	public void moveRight()
	{
		if (this.isSafeMove(1, 0)) this.moveBlock(1, 0);
	}
	
	
	/**
	 * Get the lowest cell postion. ie the cell in the block nearest the top of the board
	 * @return
	 */
	private int getLowestCellPosition()
	{
		int lowest=cells.get(0).y;
		
		for (int i=1; i<cells.size(); i++) {
			int cur=cells.get(i).y;
			if (cur<lowest) lowest=cur;
		}
		
		return lowest;
	}
	
	
	/**
	 * Get the highest cell postion. ie the cell in the block nearest the bottom of the board
	 * @return
	 */
	private int getHighestCellPosition()
	{
		int highest=cells.get(0).y;
		
		for (int i=1; i<cells.size(); i++) {
			int cur=cells.get(i).y;
			if (cur>highest) highest=cur;
		}
		
		return highest;
	}
	
	
	/**
	 * Get if any cells of a block are above this block
	 * @return
	 */
	public boolean isBlockAbove(Block b)
	{
		return this.getHighestCellPosition()<b.getLowestCellPosition();
	}
}
